#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import json
from multiprocessing import Manager, cpu_count
import os
import sys

from common.cleaner import clear
from common.common import exter_attack
from common.common_models import SubJobModel, LogDetail, SubJobDetails
from common.const import SysData, SubJobStatusEnum
from exchange import log
from exchange.backup.exchange_mailbox_exec_backup import ExchangeMailBoxService
from exchange.commons.command import Commands
from exchange.commons.common import read_temp_file, check_del_dir_or_file, output_action_result, \
    output_result_file, get_key_value, report_job_details_by_rpc, mailbox_anonymization, \
    keep_powershell_escape_character, get_windows_server_version
from exchange.commons.const import ExchangeCode, ParamConstant, BodyErr, ExchangeWindowsPath, ExchangeReportDBLabel
from exchange.commons.exchange_exception import ExchangeInternalException
from exchange.commons.powershell import execute_with_output_bool, execute
from exchange.restore.exchange_restore_base import ExchangeRestoreBase
from oceanbase.common.const import LogLevel

DAG_NODE = "DAG"
SINGLE_NODE = "Single"


class ExchangeMailboxRestoreComm(ExchangeRestoreBase):
    m_func_type = ""
    m_check_output = {'code': ExchangeCode.FAILED.value, 'bodyErr': 0, 'message': ''}

    def __init__(self, pid, job_id, sub_job_id):
        log.info("init start!")
        self._exchange_object = None
        self._database_type = ""
        self.pid = pid
        self._job_id = job_id
        self._sub_job_id = sub_job_id
        self.m_username = ""
        self.m_password = ""
        self.guid = ""
        self.mailbox_address = ""
        self.anonymization_mailbox = ""
        self.data_hub = ""
        self.meta_hub = ""
        self.m_progress = 50
        self.m_progress_status = SubJobStatusEnum.RUNNING.value
        self.job_type = ""
        self.backup_start_time = 0
        self.manager = Manager()
        self.process_shared_dict = self.manager.dict()
        self.process_num = min(cpu_count(), 8)
        self.windows_version = ""

        file_path = os.path.join(ParamConstant.PARAM_FILE_PATH, f"param{self.pid}")
        json_param = read_temp_file(file_path)
        check_del_dir_or_file(file_path)
        self._json_param = json_param
        self.compatibility_mode = \
            self._json_param.get("job").get("targetEnv").get("extendInfo").get("compatibility_mode", "false")

        super().__init__(self.pid, self._job_id, self._sub_job_id, self._json_param)

    @exter_attack
    def allow_restore_in_local_node(self):
        """
        恢复校验
        :return: True 运行成功, False 运行失败
        """
        log.info("allow_restore_in_local_node start!")
        # 获取要恢复的邮箱
        self.mailbox_address = \
            self._json_param.get("job").get("copies")[-1].get("protectObject").get("extendInfo").get(
                "PrimarySmtpAddress")
        self.anonymization_mailbox = mailbox_anonymization(self.mailbox_address)
        ret = self.check_exchange_mailbox_exist(self.mailbox_address)
        if not ret:
            log.error(f"mailbox: {self.anonymization_mailbox} restore check exists FAILED!")
            output_action_result(self.pid, ExchangeCode.FAILED.value, BodyErr.ERR_PLUGIN_CANNOT_BACKUP.value, "")
            return False
        log.info(f"mailbox: {self.anonymization_mailbox} restore allow_backup_in_local_node SUCCESS!")
        output_action_result(self.pid, ExchangeCode.SUCCESS.value, 0, "")
        return True

    # 前置任务
    @exter_attack
    def restore_prerequisite(self):
        """
        恢复前置任务
        :return: True 执行成功，False 执行失败
        """
        log.info("mailbox restore_prerequisite start!")
        self.m_progress = 50
        self.m_progress_status = SubJobStatusEnum.RUNNING.value
        # 获取要恢复的邮箱 Guid
        self.guid = self._json_param.get("job").get("copies")[-1].get("protectObject").get("id")
        self.get_data_hub_and_meta_hub_path()
        if not os.path.exists(self.data_hub):
            log.error(f"can not restore due to data path: {self.data_hub} not exists!")
            log_detail = LogDetail(logInfo=ExchangeReportDBLabel.COPY_VERIFICATION_FAILED,
                                   logLevel=LogLevel.ERROR.value,
                                   logDetail=BodyErr.COPY_META_DATA_NOT_EXIST.value)
            self.write_progress_to_file(SubJobStatusEnum.FAILED.value, 100, log_detail, "common")
            return False
        if not os.path.exists(self.meta_hub):
            log.error(f"can not restore due to meta path: {self.meta_hub} not exists!")
            log_detail = LogDetail(logInfo=ExchangeReportDBLabel.COPY_VERIFICATION_FAILED,
                                   logLevel=LogLevel.ERROR.value,
                                   logDetail=BodyErr.COPY_META_DATA_NOT_EXIST.value)
            self.write_progress_to_file(SubJobStatusEnum.FAILED.value, 100, log_detail, "common")
            return False

        output_action_result(self.pid, ExchangeCode.SUCCESS.value, 0, "")
        log.info("FINISH output_action_result!!!")
        self.write_progress_to_file(SubJobStatusEnum.COMPLETED.value, 100, "", "common")
        log.info("restore_prerequisite SUCCESS!")
        return True

    def get_data_hub_and_meta_hub_path(self):
        self.meta_hub = os.path.join(self.get_unc_path(), "meta")
        self.data_hub = os.path.join(self.get_unc_path(), "data")
        log.info("get_data_hub_and_meta_hub_path finished!")

    def get_unc_path(self):
        return ExchangeWindowsPath.UNC + \
               self._json_param.get('job').get("copies")[-1].get('repositories')[2].get('path')[0]

    @exter_attack
    def restore_gen_sub_job(self):
        """
        生成子任务
        :return: True 运行成功, False 运行失败
        """
        log.info("mailbox restore_gen_sub_job start!")
        node_name = self._json_param.get("job").get("copies")[-1].get("protectObject").get("extendInfo").get(
            "PSComputerName")
        response = []
        node_type = self.check_node_type(node_name)
        if node_type == SINGLE_NODE or node_type == DAG_NODE:
            node_id = self.get_server_id(node_name)
            response.append(
                SubJobModel(jobId=self._job_id, subJobId=self._sub_job_id, jobType=2, jobName="ExchangeMailboxRestore",
                            execNodeId=node_id, ignoreFailed=True).dict(by_alias=True))
        else:
            log.error(f"unknown node_type: {node_type}")
            return False
        output_result_file(self.pid, response)
        log.info("mailbox restore_gen_sub_job SUCCESS!")
        return True

    @exter_attack
    def progress_comm(self):
        """
        进度上报
        :return: True 运行成功, False 运行失败
        """
        log.info("mailbox backup progress_com Start!!!")
        self.report_progress_comm()
        log.info("mailbox backup progress_com Finished!!!")
        return True

    @exter_attack
    def restore(self):
        """
        备份
        :return: True 运行成功, False 运行失败
        """
        log_details = [
            LogDetail(logInfo=ExchangeReportDBLabel.START_RESTORE_LABEL,
                      logInfoParam=[self._sub_job_id], logLevel=LogLevel.INFO.value,
                      log_detail_param=[])
        ]
        output = SubJobDetails(taskId=self._job_id, subTaskId=self._sub_job_id, progress=5,
                               logDetail=log_details, taskStatus=SubJobStatusEnum.RUNNING.value).dict(by_alias=True)
        report_job_details_by_rpc(self._job_id, self.pid, self._sub_job_id, output)
        log.info("mailbox Restore Start!")
        try:
            # 获取要恢复的邮箱 Guid
            self.guid = self._json_param.get("job").get("copies")[-1].get("protectObject").get("id")
            # 获取要恢复的邮箱
            self.mailbox_address = \
                self._json_param.get("job").get("copies")[-1].get("protectObject").get("extendInfo").get(
                    "PrimarySmtpAddress")

            self.m_progress = 50
            self.m_progress_status = SubJobStatusEnum.RUNNING.value
            self.get_data_hub_and_meta_hub_path()
            final_data_path = self.data_hub
            final_data_path = final_data_path.replace("/", "\\")
            final_meta_path = self.meta_hub
            final_meta_path = final_meta_path.replace("/", "\\")
            self.anonymization_mailbox = mailbox_anonymization(self.mailbox_address)
            self.windows_version = get_windows_server_version()
            log.info(
                f"restore guid: {self.guid}, mailbox: {self.anonymization_mailbox}, input_data_hub: {final_data_path}, "
                f"input_meta_hub: {final_meta_path}, win_ver: {self.windows_version}")
            restore_strategy = self._json_param.get("job", {}).get("extendInfo", {}).get("fileReplaceStrategy", "0")
            restore_sub_objects = self._json_param.get("job", {}).get("restoreSubObjects", [])
            return_code_set = self.exec_restore(final_data_path, final_meta_path, restore_strategy, restore_sub_objects)
            self.report_restore_result(return_code_set)
        except Exception as ex:
            self.deal_with_restore_other_error(ex)
            return False
        finally:
            clear(self.m_password)
        return True

    def report_restore_result(self, return_code_set):
        log.info(f"mailbox: {self.anonymization_mailbox} restore return_code_set {return_code_set}!")
        if not return_code_set:
            task_status = SubJobStatusEnum.COMPLETED.value
            log_details = [
                LogDetail(logInfo=ExchangeReportDBLabel.SUB_JOB_SUCCESS,
                          logInfoParam=[self._sub_job_id], logLevel=LogLevel.INFO.value,
                          log_detail_param=[])
            ]
        else:
            log_details = [self.get_log_detail_from_error_code(error_code) for error_code in return_code_set]
            task_status = SubJobStatusEnum.FAILED.value
        output = SubJobDetails(taskId=self._job_id, subTaskId=self._sub_job_id, progress=100,
                               logDetail=log_details, taskStatus=task_status).dict(
            by_alias=True)
        report_job_details_by_rpc(self._job_id, self.pid, self._sub_job_id, output)
        log.info(f"mailbox: {self.anonymization_mailbox} restore SUCCESS!")

    def exec_restore(self, final_data_path, final_meta_path, restore_strategy, restore_sub_objects) -> set[int]:
        mail_service = ExchangeMailBoxService(mailbox_name=self.mailbox_address,
                                              username=self.m_username,
                                              password=self.m_password,
                                              data_hub=final_data_path,
                                              meta_hub=final_meta_path,
                                              job_id=self._job_id,
                                              pid=self.pid,
                                              process_num=self.process_num,
                                              windows_version=self.windows_version,
                                              sub_job_id=self._sub_job_id,
                                              compatibility_mode=self.compatibility_mode)
        # 初始化进程共享变量，统计恢复邮件数
        self.process_shared_dict["restore_email_success_count"] = self.manager.list([0] * self.process_num)
        self.process_shared_dict["restore_email_fail_count"] = self.manager.list([0] * self.process_num)
        return mail_service.restore_entry(restore_strategy, restore_sub_objects, self.process_shared_dict)

    def get_log_detail_from_error_code(self, error_code):
        # 数据库状态异常，需要添加参数
        if error_code == BodyErr.BACKUP_FAIL_FOR_DATABASE_STATE_ABNORMAL.value:
            error_param = [
                self._json_param.get('job').get("copies")[-1].get('protectObject').get('extendInfo').get('DatabaseName')
            ]
        else:
            error_param = []
        return LogDetail(logInfo=ExchangeReportDBLabel.SUB_JOB_FALIED,
                         logInfoParam=[self._sub_job_id], logLevel=LogLevel.ERROR.value,
                         logDetail=error_code, logDetailParam=error_param)

    def deal_with_restore_other_error(self, error):
        log_detail = self.get_log_detail_from_error_code(BodyErr.ERROR_INTERNAL.value)
        output = SubJobDetails(taskId=self._job_id, subTaskId=self._sub_job_id, progress=100,
                               logDetail=[log_detail], taskStatus=SubJobStatusEnum.FAILED.value, dataSize=0).dict(
            by_alias=True)
        log.error(error, exc_info=True)
        report_job_details_by_rpc(self._job_id, self.pid, self._sub_job_id, output)
        log.info(f"mailbox: {self.anonymization_mailbox} restore Failed!")

    @exter_attack
    def restore_post_job(self):
        """
        后置任务
        :return: True 运行成功, False 运行失败
        """
        log.info("restore_post_job Start!")
        output_action_result(self.pid, ExchangeCode.SUCCESS.value, 0, "")
        self.write_progress_to_file(SubJobStatusEnum.COMPLETED.value, 100, "", "common")
        log.info("restore_post_job SUCCESS!")
        return True

    def check_exchange_mailbox_exist(self, mailbox_name):
        """
        检测邮箱是否存在
        :return: True 运行成功, False 运行失败
        """
        log.info("check_exchange_mailbox_exist start!")
        mailbox_name = keep_powershell_escape_character(mailbox_name)
        result = execute_with_output_bool(self.m_username, self.m_password,
                                          Commands.IS_MAILBOX_EXIST.format(mailbox_name=mailbox_name))
        if result.get("Output") == "True":
            return True
        else:
            log.error("check_exchange_mailbox_exist ReturnCode: %s, ErrorMessage: %s", result.get("ReturnCode", ""),
                         result.get("Output", ""))
            return False

    @exter_attack
    def query_job_permission(self):
        output_action_result(self.pid, ExchangeCode.SUCCESS.value, 0, "")
        return True

    def check_node_type(self, node_name):
        log.info("check_node_type start!")
        result = execute(self.m_username, self.m_password,
                         Commands.GET_ALL_EXCHANGE_SERVER.format())
        return_code = result.get("ReturnCode", "")
        if return_code != "0":
            log.error(f"get_all_exchange_server ReturnCode: {return_code}, ErrorMessage: {result.get('Output', '')}")
            return ""
        if not result.get("Output", []):
            log.error(f"get_all_exchange_server ReturnCode: {return_code}, ErrorMessage: {result.get('Output', '')}")
            return ""

        result_obj = json.loads(result.get("Output"))
        if not isinstance(result_obj, list):
            result_obj = [result_obj]
        total_servers = [_.get("Name", "").upper() for _ in result_obj]
        if node_name.upper() not in total_servers:
            log.error(f"get_all_exchange_server node_name {node_name} not in server_names: {total_servers}")
            return ""

        result = execute(self.m_username, self.m_password,
                         Commands.GET_DATABASE_AVAILABLE_GROUP.format())
        return_code = result.get("ReturnCode", "")
        if return_code != "0":
            log.error(
                f"get_database_available_group ReturnCode: {return_code}, ErrorMessage: {result.get('Output', '')}")
            return ""
        if not result.get("Output", []):
            return SINGLE_NODE
        result_obj = json.loads(result.get("Output"))
        if not isinstance(result_obj, list):
            result_obj = [result_obj]
        dag_group_servers = [_.get("Servers", "") for _ in result_obj]
        for dag_group_server in dag_group_servers:
            if node_name.upper() in [_.upper() for _ in dag_group_server]:
                return DAG_NODE
        return SINGLE_NODE

    def get_server_id(self, server_name):
        """
        获取节点id
        :return: 节点id
        """
        log.info("get_server_id start!")
        result = execute(self.m_username, self.m_password,
                         Commands.GET_SPECIFIED_EXCHANGE_SERVER.format(server_name=server_name))
        return_code = result.get("ReturnCode", "")
        if return_code != "0":
            log.error(f"get_server_id ReturnCode: {return_code}, ErrorMessage: {result.get('Output', '')}")
            return ""
        result_obj = json.loads(result.get("Output"))
        return result_obj.get("Guid", "")

    @exter_attack
    def progress_comm(self):
        """
        进度上报
        :return: True 运行成功, False 运行失败
        """
        log.info("mailbox restore progress_com Start!!!")
        self.report_progress_comm()
        log.info("mailbox restore progress_com Finished!!!")
        return True


def do_work(args_list):
    log.info("exchange mailbox restore do_work start!")
    cmd = args_list[0]
    pid = args_list[1]
    job_id = ""
    sub_job_id = ""
    if len(args_list) >= 3:
        job_id = args_list[2]
    if len(args_list) == 4:
        sub_job_id = args_list[3]
    restore_comm_inst = ExchangeMailboxRestoreComm(pid, job_id, sub_job_id)
    flags, restore_comm_inst.m_username = get_key_value(f'job_targetEnv_auth_authKey_{pid}')
    if not flags:
        log.error("get username FAILED")
        return False
    flags, restore_comm_inst.m_password = get_key_value(f'job_targetEnv_auth_authPwd_{pid}')
    if not flags:
        log.error("get user password FAILED")
        return False

    cmd_dict = {
        "AllowRestoreInLocalNode": [restore_comm_inst.allow_restore_in_local_node],
        "RestorePrerequisite": [restore_comm_inst.restore_prerequisite],
        "RestoreGenSubJob": [restore_comm_inst.restore_gen_sub_job],
        "progress_comm": [restore_comm_inst.progress_comm],
        "Restore": [restore_comm_inst.restore],
        "RestorePostJob": [restore_comm_inst.restore_post_job]
    }
    func_array = cmd_dict.get(cmd)
    if not func_array:
        output_action_result(pid, ExchangeCode.FAILED.value, BodyErr.ERROR_INTERNAL.value, f"Cmd Param error.")
        return False
    try:
        ret = func_array[0]()
    except Exception as err:
        log.error(f"Exec cmd failed as err: {str(err)} pid: {pid} jobId: {job_id}, cmd:{cmd}")
        return False
    finally:
        clear(SysData.SYS_STDIN)
    return ret


if __name__ == "__main__":
    log.info("Running exchange mailbox restore main....")
    for line in sys.stdin:
        SysData.SYS_STDIN = line
        break
    args = sys.argv[1:]
    if len(args) < 2:
        log.error("Not enough parameters, param cnt: %s.", len(args))
        sys.exit(1)
    if not do_work(args):
        sys.exit(0)
